package net.crozz.openapi.http;

import com.google.gson.Gson;
import net.crozz.openapi.ApiException;
import org.apache.commons.httpclient.*;
import org.apache.commons.httpclient.auth.AuthScope;
import org.apache.commons.httpclient.cookie.CookiePolicy;
import org.apache.commons.httpclient.methods.GetMethod;
import org.apache.commons.httpclient.methods.PostMethod;
import org.apache.commons.httpclient.methods.multipart.*;
import org.apache.commons.httpclient.params.HttpClientParams;
import org.apache.commons.httpclient.params.HttpConnectionManagerParams;
import org.apache.commons.httpclient.params.HttpMethodParams;
import org.apache.commons.httpclient.protocol.Protocol;
import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;

import java.io.IOException;
import java.lang.reflect.Type;
import java.util.LinkedList;
import java.util.List;

public class Requester {

    private static final Logger log = Logger.getLogger(Requester.class.getName());
    boolean debug = true;
    private HttpClient client = null;

    public Requester(RequesterOptions options) {
        debug = options.isDebug();

        MultiThreadedHttpConnectionManager connectionManager = new MultiThreadedHttpConnectionManager();
        HttpConnectionManagerParams params = connectionManager.getParams();
        params.setDefaultMaxConnectionsPerHost(options.getMaxConPerHost());
        params.setConnectionTimeout(options.getConTimeOutMs());
        params.setSoTimeout(options.getSoTimeOutMs());

        HttpClientParams clientParams = new HttpClientParams();
        clientParams.setCookiePolicy(CookiePolicy.IGNORE_COOKIES);

        client = new org.apache.commons.httpclient.HttpClient(clientParams, connectionManager);
        if (options.getProxyHost() != null) {
            client.getHostConfiguration().setProxy(options.getProxyHost(), options.getProxyPort());
            client.getParams().setAuthenticationPreemptive(true);
            if (options.getProxyUser() != null) {
                client.getState().setProxyCredentials(
                        AuthScope.ANY,
                        new UsernamePasswordCredentials(options.getProxyUser(), options.getProxyPassword())
                );
            }
        }
        Protocol.registerProtocol("https", new Protocol("https", new MySSLSocketFactory(), 443));
    }

    public static String makeUrl(String url, Param... params) {
        if (null == params || params.length == 0) {
            return url;
        }

        List<String> list = new LinkedList<String>();
        for (Param i : params) {
            if (!i.isHeader || i.value != null) {
                list.add(i.URLEncode());
            }
        }

        if (list.isEmpty()) {
            return url;
        }
        if (url.contains("?")) {
            return url + "&" + StringUtils.join(list, '&');
        }
        return url + "?" + StringUtils.join(list, '&');
    }

    private void log(String message) {
        if (debug) {
            System.out.println(message);
            log.debug(message);
        }
    }

    public byte[] getBytes(String url, Param... params) throws ApiException {
        return httpRequest(new GetMethod(makeUrl(url, params)));
    }

    public String getStr(String url, Param... params) throws ApiException {
        String str = new String(getBytes(url, params));
        log("\n" + str + "\n");
        return str;
    }

    public <T> T getObj(Class<T> clazz, String url, Param... params) throws ApiException {
        return new Gson().fromJson(getStr(url, params), clazz);
    }

    public <T> T getObj(Type type, String url, Param... params) throws ApiException {
        return new Gson().fromJson(getStr(url, params), type);
    }

    public byte[] postBytes(String url, Param... params) throws ApiException {
        PostMethod postMethod = new PostMethod(url);
        for (Param i : params) {
            if (i.isHeader) {
                log("header: " + i.name + "->" + i.value);
                postMethod.addRequestHeader(i.name, i.value);
            } else if (i.value != null) {
                log("post: " + i.name + "->" + i.value);
                postMethod.addParameter(i.name, i.value);
            }
        }
        HttpMethodParams param = postMethod.getParams();
        param.setContentCharset("UTF-8");
        return httpRequest(postMethod);
    }

    public String postStr(String url, Param... params) throws ApiException {
        String str = new String(postBytes(url, params));
        log("\n" + str + "\n");
        return str;
    }

    public <T> T postObj(Class<T> clazz, String url, Param... params) throws ApiException {
        return new Gson().fromJson(postStr(url, params), clazz);
    }

    public <T> T postObj(Type type, String url, Param... params) throws ApiException {
        return new Gson().fromJson(postStr(url, params), type);
    }

    public byte[] multiPartBytes(String url, Param... params) throws ApiException {
        PostMethod postMethod = new PostMethod(url);

        List<Param> list = new LinkedList<Param>();
        for (Param i : params) {
            if (i.isHeader) {
                log("header: " + i.name + "->" + i.value);
                postMethod.addRequestHeader(i.name, i.value);
            } else if (i.value != null || i.content != null) {
                list.add(i);
            }
        }
        Part[] parts = new Part[list.size()];
        int i = 0;
        for (Param j : list) {
            if (j.value != null) {
                log("post: " + j.name + "->" + j.value);
                parts[i++] = new StringPart(j.name, j.value);
            } else {
                parts[i++] = new FilePart(j.name, new ByteArrayPartSource(j.filename, j.content));
            }
        }

        postMethod.setRequestEntity(new MultipartRequestEntity(parts, postMethod.getParams()));

        return httpRequest(postMethod);
    }

    public String multiPartStr(String url, Param... params) throws ApiException {
        String str = new String(multiPartBytes(url, params));
        log("\n" + str + "\n");
        return str;
    }

    public <T> T multiPartObj(Class<T> clazz, String url, Param... params) throws ApiException {
        return new Gson().fromJson(multiPartStr(url, params), clazz);
    }

    public <T> T multiPartObj(Type type, String url, Param... params) throws ApiException {
        return new Gson().fromJson(multiPartStr(url, params), type);
    }

    public byte[] httpRequest(HttpMethod method) throws ApiException {
        try {
            log(method.getName() + " " + method.getURI().toString());
        } catch (URIException ignored) {
        }
        int respCode = -1;
        try {
            method.getParams().setParameter(HttpMethodParams.RETRY_HANDLER, new DefaultHttpMethodRetryHandler(3, false));
            client.executeMethod(method);
            Header[] resHeader = method.getResponseHeaders();
            respCode = method.getStatusCode();

            log("Response:" + String.valueOf(respCode));
            for (Header header : resHeader) {
                log(header.getName() + ": " + header.getValue());
            }

            byte[] respBody = method.getResponseBody();
            if (respCode != 200) {
                throw new ApiException(method.getURI().toString(), respCode, new String(respBody));
            }
            return respBody;
        } catch (IOException ioe) {
            try {
                throw new ApiException(ioe, method.getURI().toString(), respCode, null);
            } catch (Exception ignored) {
            }
            return null;
        } finally {
            method.releaseConnection();
        }
    }

}
